import { Injectable } from '@angular/core';
import { Subject, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';

export class SelectOption {
  constructor(
    public value: any,
    public label: any,
    public selected: boolean = false,
  ) {}
}

export class SelectEvent {
  options: SelectOption[];
  // 是否是用户点击等事件触发
  isUserEvent: boolean;
}

export type SelectMode = 'single' | 'multiple';

@Injectable()
export class SelectStoreService {
  /** Select mode, 'single' or 'multiple'. */
  mode: SelectMode = 'single';

  /** Option store. */
  options: SelectOption[] = [];

  /** Current selected option's label. */
  selectedLabel: string = '';

  /** All selected option's values. */
  selectedValues: any[] = [];

  /** Observe selected options. */
  selectOptionChange: Observable<SelectEvent>;

  /** Observe selected option values. */
  selectValuesChange: Observable<any>;

  private selectSubject: Subject<SelectEvent> = new Subject();

  /** Get selected options. */
  get selectedOptions(): SelectOption[] {
    return this.options.filter((o) => o.selected);
  }

  /** Get option count. */
  get optionCount(): number {
    return this.options.length;
  }

  /** Get if any option is selected. */
  get hasSelected(): boolean {
    return this.selectedOptions.length > 0;
  }

  /** Get if all options are selected. */
  get allSelected(): boolean {
    return this.options.length > 0 && this.selectedOptions.length === this.options.length;
  }

  constructor() {
    this.selectOptionChange = this.selectSubject.asObservable();

    this.selectValuesChange = this.selectOptionChange.pipe(
      filter((e) => e.isUserEvent),
      map((e) => {
        const values = e.options.map((o) => o.value);
        if (this.mode === 'single') {
          return values.length > 0 ? values[0] : undefined;
        }
        return values;
      }),
    );

    this.selectOptionChange.subscribe(() => {
      this.selectedLabel = this.formatLabel();
    });
  }

  /** Add an option to the option store. */
  addOption(option: SelectOption) {
    this.options.push(option);
    if (~this.selectedValues.indexOf(option.value)) {
      this.select(option);
      this.onSelectChange();
    }
  }

  /** Remove an option from the option store. */
  removeOption(option: SelectOption) {
    const idx = this.options.indexOf(option);
    this.options.splice(idx, 1);
    if (option.selected) {
      this.onSelectChange();
    }
  }

  /** Toggle option select state. */
  toggleSelect(option: SelectOption) {
    if (this.mode === 'single') {
      this.select(option);
    } else {
      option.selected = !option.selected;
    }
    this.onSelectChange(true);
  }

  /** Only for 'single' mode. Select an option. */
  select(option: SelectOption) {
    if (option.selected) {
      return;
    }
    this.options.forEach((o) => o.selected = false);
    option.selected = true;
  }

  /** Select all options. */
  selectAll(value: boolean) {
    this.options.forEach((o) => o.selected = value);
    this.onSelectChange(true);
  }

  /** Manually clear selection. */
  clear() {
    this.options.forEach((o) => o.selected = false);
    this.onSelectChange(true);
  }

  /** Select option by values. */
  selectByValues(values: any[]) {
    // 如果单选，则只能选择一个值
    if (this.mode === 'single') {
      values = values.slice(0, 1);
    }
    this.selectedValues = values;
    this.options.forEach((option) => {
      option.selected = values.indexOf(option.value) > -1;
    });
    this.onSelectChange();
  }

  /** Update select option content. */
  updateOption(option: SelectOption, updateBody: {value?: any; label?: any}) {
    Object.assign(option, updateBody);
    if (option.selected) {
      this.onSelectChange();
    }
  }

  /** Destroy select store */
  destroy() {
    this.selectSubject.complete();
  }

  private onSelectChange(isUserEvent = false) {
    this.selectSubject.next({
      options: this.selectedOptions,
      isUserEvent,
    });
  }

  private formatLabel() {
    return this.selectedOptions.map((o) => o.label).join(', ');
  }
}
